package org.sensornet2010.sensmote;

import java.io.IOException;
import java.util.Date;

import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Form;
import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.ImageItem;
import javax.microedition.lcdui.Item;
import javax.microedition.lcdui.StringItem;
import javax.microedition.lcdui.TextBox;
import javax.microedition.lcdui.TextField;
import javax.microedition.media.Manager;
import javax.microedition.media.Player;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;

import org.sensornet2010.alpha.Configuration;
import org.sensornet2010.alpha.RemoteServer;
import org.sensornet2010.alpha.SensorData;
import org.sensornet2010.alpha.SoundRecorder;
import org.sensornet2010.core.Storage;

/*
 * NAMING POLICIES:
 * 
 *  showXXXDisplay() synchronous  
 *  startXXXAction() asynchronous action
 *  updateXXXDisplay() synchronous
 *  
 *  xxxCommand  
 */

public class MoteMidlet extends MIDlet  implements CommandListener {
	
	// Commands
	private Command exitCmd;
	private Command connectCmd;
	private Command updateConfigCmd;
	private Command configCmd;
	private Command measureCmd;
	private Command debugCmd;
	private Command backInitializationCmd;
	private Command backMeasureCmd;
		
	// GUI components
	private Display		display; 				// The display for this MIDlet
	private Form 		measurementForm;
	private ImageItem	animationItem;
	private Form		initializationForm;
	private StringItem	initializationStatusString;
	private StringItem	measurementStatusString;
	public static StringItem	globalDebug = new StringItem("Global:", "\n");
	private Form 		configurationForm;
	
	private RemoteServer 	server;
	private SoundRecorder	soundRecorder;
	private long 			counter;
	private Storage 		storage;

	private static String RESOURCE_IMAGE_SPLASH		= "/icons/meter_160x90.png";
	private static String RESOURCE_IMAGE_ANIM_A		= "/icons/molecule_48x23.png";
	private static String RESOURCE_IMAGE_ANIM_B		= "/icons/molecule2_48x23.png";
	
	public static String STORAGE_KEY_SENSORID		= "id";
	
	private Image animationImageA;
	private Image animationImageB;
	
	private DebugInformation debug;

	private static String MEASURING_DISPLAY_TITLE = "Measuring";
	
	public MoteMidlet() {
		this.display = Display.getDisplay(this);
		
		this.exitCmd = new Command("Exit", Command.EXIT, 1);
		this.connectCmd = new Command("Connect", Command.SCREEN, 1);
		this.updateConfigCmd = new Command("Update", Command.SCREEN, 1);
		this.configCmd = new Command("Configuration", Command.SCREEN, 1);
		this.measureCmd = new Command("Measure", Command.SCREEN, 1);
		this.debugCmd = new Command("Debug", Command.SCREEN, 1);
		this.backInitializationCmd = new Command("Back", Command.BACK, 1);
		this.backMeasureCmd = new Command("Measure", Command.BACK, 1);

		
		this.debug = new  DebugInformation();
		this.storage = new Storage();

		this.measurementForm = buildMeasurementDisplay();
		this.initializationForm = buildInitializationDisplay();
		this.configurationForm = buildConfigurationDisplay();

		
		//debug configuration
		Configuration.RECORDING_INTERVAL = 1; // 1 seconds
	}

	private Form buildMeasurementDisplay(){
		try {
			this.animationImageA = Image.createImage(RESOURCE_IMAGE_ANIM_A);
			this.animationImageB = Image.createImage(RESOURCE_IMAGE_ANIM_B);
		} catch (IOException e) {
			this.debugError(e);
		}
		this.animationItem = new ImageItem("Measuring", this.animationImageA, Item.LAYOUT_CENTER, "A");
		
		Form f = new Form(MEASURING_DISPLAY_TITLE);
		f.append(this.animationItem);
		f.append(MoteMidlet.globalDebug);
		this.measurementStatusString = new StringItem( "Recorded Value:", "\n");
		f.append(this.measurementStatusString);

		//f.append("\nRecorded value:\n 348 PPM\n");
		//Date date = new Date();
		//f.append(date.toString());
		f.addCommand(debugCmd);
		f.addCommand(exitCmd);
		f.setCommandListener(this);
		return f;
	}

	private Form buildInitializationDisplay(){
		Form f = new Form("Initialization");
		this.initializationStatusString = new StringItem("Testing connections", "\n...");
		f.append(this.initializationStatusString);
		f.addCommand(measureCmd);
		f.addCommand(configCmd);
		f.addCommand(debugCmd);
		f.addCommand(exitCmd);
		f.setCommandListener(this);
		return f;
	}

	private Form buildConfigurationDisplay(){

		Form f = new Form("Configuration");
		f.append( new TextField("Server:", "server.sensornet.org", 25, TextField.ANY) );
		f.append( new TextField("Sensor ID:", this.getDeviceId(), 12, TextField.ANY) );
		f.append("Advanced options:\n");
		f.append( new TextField("Interval (seconds):", "60",4, TextField.DECIMAL) );
		f.append( new TextField("Duration (millis):", "2000", 4, TextField.DECIMAL) );
		f.addCommand(this.updateConfigCmd);
		f.addCommand(exitCmd);
		f.setCommandListener(this);
		return f;
	}

	
	protected void destroyApp(boolean unconditional) {
		this.debug("APP destroyed");
	}

	protected void pauseApp() {
		this.debug("APP paused");
	}

	protected void startApp() throws MIDletStateChangeException {
		this.debug("APP started");
		this.showWelcomeDisplay();
	}

	public void commandAction(Command c, Displayable d) {
		if (c == exitCmd) {
			destroyApp(false);
			notifyDestroyed();
		} else if (c == this.connectCmd) {
			this.showInitializationDisplay();
			this.startInitialization();
		} else if (c == this.configCmd) {
			this.showConfigurationDisplay();
		} else if (c == this.debugCmd) {
			// Two different return screens for this display
			Command backCmd = this.backInitializationCmd;
			if (d.getTitle() == MEASURING_DISPLAY_TITLE) 
				backCmd = this.backMeasureCmd;
			this.showDebugDisplay(backCmd);
		} else if (c == this.backInitializationCmd) {
			this.showInitializationDisplay();
		} else if (c == this.backMeasureCmd) {
			this.showMeasurementDisplay();
		} else if (c == this.updateConfigCmd) {
			this.updateConfiguration();
			this.showInitializationDisplay();
			this.startInitialization();
		} else if (c == this.measureCmd) {
			this.showMeasurementDisplay();
			this.startMeasurement();
		}
	}
	
	private void showWelcomeDisplay(){
		Form f = new Form("Welcome");
		f.append("SENSORNET MOTE\nVersion 1.0");
		try {
			Image img = Image.createImage(RESOURCE_IMAGE_SPLASH);
			f.append(img);
		} catch (IOException e) {
			this.debugError(e);
		}
		f.append("\nCompatibility information");
		f.addCommand(connectCmd);
		f.addCommand(configCmd);
		f.addCommand(exitCmd);
		f.setCommandListener(this);
		display.setCurrent(f);
	}
	
	
	private void showInitializationDisplay(){
		display.setCurrent(this.initializationForm);
	}
	
	private String getDeviceId(){
		String deviceId = "Mote-";
		try {
			deviceId = storage.getValue(STORAGE_KEY_SENSORID);
			System.err.println("Device-id:" + deviceId);
//			if ( deviceId == null)
//				deviceId = javax.bluetooth.LocalDevice.getLocalDevice().getBluetoothAddress();
//				// this code generates an non-captured exception in
		} catch (Exception e) {
			deviceId = "ERROR:" + e.getMessage();
			this.debugError(e);
		}
		return deviceId;
	}

	private void showConfigurationDisplay(){
		String deviceId = storage.getValue(STORAGE_KEY_SENSORID);
		System.err.println("Device-id:" + deviceId);
		display.setCurrent(this.configurationForm);
	}
	

	private void showMeasurementDisplay(){
		display.setCurrent(this.measurementForm);
	}

	private void showDebugDisplay(Command backCmd){
		Form f = new Form("Debug");
		//f.append(this.debug.getDebugInfoText());
		f.append( this.storage.getDebugInfo());
		f.addCommand(backCmd);
		f.addCommand(exitCmd);
		f.setCommandListener(this);
		display.setCurrent(f);
	}
	
	private void startInitialization(){
		//asynchronous method
		server = null;
		soundRecorder = null;
		initializationStatusString.setText("");
		Thread t = new Thread(new Runnable() {
			public void run() {
				try {
					String info ="\n";
					server = new RemoteServer(Configuration.SERVER_URL);
					soundRecorder = new SoundRecorder(Configuration.RECORDING_DURATION);
					// test audio capture
					byte[] sound = soundRecorder.record();
					debug( "Recorded sound " + sound.length );
					info+= "Sensor: OK\n";
					initializationStatusString.setText(info);
					// test server connection
					String data = server.getStatus();
					debug( "Connected to server:\n status=" + data);
					info+= "Server: OK";
					initializationStatusString.setText(info);
					debug("Initialization done");
				} catch (Exception e) {
					debugError(e);
				};
			}
		});
		t.start();
		this.debug("Initialization done");
	}
	private void startMeasurement(){
		//asynchronous method
		//this.doLoopTask();
		Thread t = new Thread(new Runnable() {
			public void run() {
				try {
					while (true){
						doMeasurement();
						//Thread.sleep(Configuration.RECORDING_INTERVAL*1000);
						Thread.sleep(500); //0.5 seconds
						//FIXME: debug
					}
				} catch (Exception e) {
					debugError(e);
				};
			}
		});
		t.start();
		
	}
	private void doMeasurement(){
		this.switchAnimation();
		String globalDebug =  "counter:" + counter + "\n" + (new Date()).toString();
		MoteMidlet.globalDebug.setText(globalDebug);
		try {
			// audio capture
			byte[] sound = this.soundRecorder.record();
			globalDebug = MoteMidlet.globalDebug.getText() + "\nrecord:" + (new Date()).toString() +":"+ sound.length;
			MoteMidlet.globalDebug.setText(globalDebug);
			// audio processing
			SensorData data = new SensorData(Configuration.SENSOR_ID, sound);
			data = null;
			// send data to server
			//String info = server.sendData(data);
			String info = server.getStatus();
			globalDebug = MoteMidlet.globalDebug.getText() + "\nsent:" + (new Date()).toString()+":" + info;
			MoteMidlet.globalDebug.setText(globalDebug);
			counter++;
			if (counter % 10 == 0)
				debug("doMeasurement: counter=" + counter);
			info = "" + counter + "\n" + (new Date()).toString();
			this.measurementStatusString.setText(info);
		} catch (Exception e) {
			debugError(e);
			globalDebug = MoteMidlet.globalDebug.getText() + "\nERROR:" + (new Date()).toString() +":" + e.getMessage() +":";
			MoteMidlet.globalDebug.setText(globalDebug);
		}
	}
	
	private void updateConfiguration(){
		int position = 1;
		TextField item = (TextField)configurationForm.get(position);
		String value = item.getString();
		storage.setValue(STORAGE_KEY_SENSORID, value);
	}
	
	private void startLoopTask(){
		System.err.println("doLoopTask>> started");
		Thread t = new Thread(new Runnable() {
			public void run() {
				try {
					while (true){
						Thread.sleep(2*1000);
						switchAnimation();
					}
				} catch (Exception e) {
					debugError(e);
				};
				System.err.println("doLoopTask>> done");
			}
		});
		t.start();
	}
	
	private void startAsynchronousTask(){
		final Displayable dsp = Display.getDisplay(this).getCurrent();
		final String title = dsp.getTitle();
		System.err.println("doAsynchronousTask>>" + title);
		//dsp.setTitle("");
		Thread t = new Thread(new Runnable() {
			public void run() {
				try {
					for (int i = 0; i < 10; i++) {
						//dsp.setTitle(dsp.getTitle() +".");
						Thread.sleep(1000/10);
						System.err.println("#");
					}
					//dsp.setTitle(title);
					System.err.println("doAsynchronousTask>> done");
				} catch (Exception e) {
					debugError(e);
				};
			}
		});
		t.start();
	}
	private void debug(String message){
		this.debug.addMessage(message);
	}
	private void debugError(Exception e){
		e.printStackTrace();
		this.debug.addMessage("ERROR:" + e.getMessage());
	}
	
	private void switchAnimation(){
		String state = this.animationItem.getAltText();
		String label = "B";
		Image img = this.animationImageB;
		if (state == "B") {
			label = "A";
			img = this.animationImageA;
		}
		this.animationItem.setImage(img);
		this.animationItem.setAltText(label);
	}
}
